package com.element.oauth2.server.global.generator;

import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;

import java.security.SecureRandom;
import java.time.Instant;
import java.util.Random;

public class OAuthAuthorizationCodeGenerator implements OAuth2TokenGenerator<OAuth2AuthorizationCode> {

    private final RandomValueStringGenerator generator = new RandomValueStringGenerator(6);

    @Nullable
    public OAuth2AuthorizationCode generate(OAuth2TokenContext context) {
        if (context.getTokenType() != null && "code".equals(context.getTokenType().getValue())) {
            Instant issuedAt = Instant.now();
            Instant expiresAt = issuedAt.plus(context.getRegisteredClient().getTokenSettings().getAuthorizationCodeTimeToLive());
            return new OAuth2AuthorizationCode(generator.generate(), issuedAt, expiresAt);
        } else {
            return null;
        }
    }

    public static class RandomValueStringGenerator {
        private static final char[] DEFAULT_CODEC = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".toCharArray();
        private Random random;
        private int length;

        public RandomValueStringGenerator() {
            this(6);
        }

        public RandomValueStringGenerator(int length) {
            this.random = new SecureRandom();
            this.length = length;
        }

        public String generate() {
            byte[] verifierBytes = new byte[this.length];
            this.random.nextBytes(verifierBytes);
            return this.getAuthorizationCodeString(verifierBytes);
        }

        protected String getAuthorizationCodeString(byte[] verifierBytes) {
            char[] chars = new char[verifierBytes.length];

            for (int i = 0; i < verifierBytes.length; ++i) {
                chars[i] = DEFAULT_CODEC[(verifierBytes[i] & 255) % DEFAULT_CODEC.length];
            }

            return new String(chars);
        }

        public void setRandom(Random random) {
            this.random = random;
        }

        public void setLength(int length) {
            if (length <= 0) {
                throw new IllegalArgumentException("length must be greater than 0");
            } else {
                this.length = length;
            }
        }
    }
}